package com.devbobcorn.nekoration.client.rendering;

import java.io.File;
import java.util.Random;

import com.devbobcorn.nekoration.blocks.entities.PhonographBlockEntity;
import com.devbobcorn.nekoration.sounds.ModSounds;
import com.devbobcorn.nekoration.sounds.music.LyricsDecoder;
import com.devbobcorn.nekoration.sounds.music.LyricsHelper;
import com.devbobcorn.nekoration.sounds.music.VisualHelper;
import com.devbobcorn.nekoration.sounds.music.LyricsHelper.Line;
import com.devbobcorn.nekoration.sounds.music.LyricsHelper.LinePiece;
import com.devbobcorn.nekoration.sounds.music.LyricsHelper.Lyrics;
import com.mojang.blaze3d.matrix.MatrixStack;

import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.renderer.IRenderTypeBuffer;
import net.minecraft.util.math.vector.Vector3f;

@SuppressWarnings("unused")
public class MusicRenderer {
    static final double frac = 0.0625D;
    static final Random random = new Random();
    private static Minecraft mc;
    private static FontRenderer font;
    private static boolean ready = false;

    // Whether to use System.currentTimeMillis() to get current time,
    // which is more accurate but cannot be used for frame-by-frame
    // rendering(Replay Mod, etc).
    public static final boolean USE_SYSTEM_TIME = false;
    private static int playedMilSecs = 0;
    private static long lastMilSec = 0;

    private static Line currentLine = null;
    private static String[] currentText = { "", "", "" }; // Played, Playing, To be played
    private static boolean fadingIn = false;
    private static int lineGradient = 0;
    private static int wordGradient = 0;

	public static void render(PhonographBlockEntity tileEntity, float partialTicks, MatrixStack stack, IRenderTypeBuffer buffers, int combinedLight, int combinedOverlay) {
        if (mc == null) {
            mc = Minecraft.getInstance();
            font = mc.font;
        }

        if (ready){
            long currentMilSecs = USE_SYSTEM_TIME ? System.currentTimeMillis() : tileEntity.getLevel().getGameTime() * 50;
            int deltaMilSecs = mc.isPaused() ? 0 : (int)(currentMilSecs - lastMilSec); // Ticks passed since last render

            if (playedMilSecs + deltaMilSecs >= ModSounds.MAX_FRAME || playedMilSecs < 0)
                playedMilSecs = 0;

            LinePiece newPiece = LyricsHelper.lyrics.getAtTime(playedMilSecs, playedMilSecs + deltaMilSecs);
            playedMilSecs += deltaMilSecs;
            if (newPiece != null){
                if (newPiece.line != currentLine){ // A new line starts...
                    currentLine = newPiece.line;
                    fadingIn = true;
                    lineGradient = 0;
                }
                currentText[0] = newPiece.prev;
                currentText[1] = newPiece.piece;
                currentText[2] = newPiece.getRest();
                wordGradient = Math.min(10, (int)(newPiece.word.duration / 30.0));
            }
            
            if (wordGradient > 0)
                wordGradient--;

            if (currentLine != null){ // Current line fades out...
                if (playedMilSecs >= currentLine.getEndTime() && lineGradient <= 4){
                    currentLine = null;
                } else if (playedMilSecs >= currentLine.getEndTime() - 100){ // Current line fades out...
                    if (lineGradient > 0){
                        lineGradient = Math.max(4, lineGradient - 9);
                        fadingIn = false;
                    }
                } else if (lineGradient < 250){ // Current line fades in...
                    lineGradient += 9;
                }
            }

            stack.pushPose(); // push the current transformation matrix + normals matrix
			stack.translate(0.5, 0.0, 0.5); // Change its pivot point before rotation...
			stack.mulPose(Vector3f.YP.rotationDegrees(tileEntity.dir * 15F));
			stack.translate(-0.5, 0.0, -0.5); // Then just get it back...
			stack.translate(tileEntity.offset[0] * frac, tileEntity.offset[1] * frac, tileEntity.offset[2] * frac);

            stack.scale((float)tileEntity.scale, -(float)tileEntity.scale, (float)tileEntity.scale);
            renderLine1(tileEntity, partialTicks, stack, buffers, combinedLight, combinedOverlay);

            stack.scale(0.1F, 0.1F, 0.1F);
            //font.draw(stack, "[" + tileEntity.type + "|" + tileEntity.line + "]Time: " + playedMilSecs, 0.0F, 0.0F, 16777215);
            font.draw(stack, "[" + VisualHelper.frames + "]", 0.0F, 0.0F, 16777215);
            stack.popPose(); // restore the original transformation matrix + normals matrix

            lastMilSec = currentMilSecs;
        }
	}

    public static final int TYPE_COUNT = 2;

    // Type 0: Fade in from below...
    private static void renderType0(PhonographBlockEntity tileEntity, float partialTicks, MatrixStack stack, IRenderTypeBuffer buffers, int combinedLight, int combinedOverlay, int baseTransparency){
        font.draw(stack, currentText[0], 0.0F, 0.0F, (baseTransparency << 24) + tileEntity.colors[0]);
        double translation = wordGradient * 0.1;
        stack.translate(font.width(currentText[0]), translation, 0.0); // Offset Vertically...
        int wordTransparency = Math.max(4, (int)((255 - wordGradient * 25) * (baseTransparency / 255.0)));
        font.draw(stack, currentText[1], 0.0F, 0.0F, (wordTransparency << 24) + tileEntity.colors[1]);
        stack.translate(-font.width(currentText[0]), -translation, 0.0);
    }

    // Type 1: Fade in from the right...
    private static void renderType1(PhonographBlockEntity tileEntity, float partialTicks, MatrixStack stack, IRenderTypeBuffer buffers, int combinedLight, int combinedOverlay, int baseTransparency){
        font.draw(stack, currentText[0], 0.0F, 0.0F, (baseTransparency << 24) + tileEntity.colors[0]);
        double translation = wordGradient * 0.1;
        stack.translate(font.width(currentText[0]) + translation, 0.0, 0.0); // Offset Vertically...
        int wordTransparency = Math.max(4, (int)((255 - wordGradient * 25) * (baseTransparency / 255.0)));
        font.draw(stack, currentText[1], 0.0F, 0.0F, (wordTransparency << 24) + tileEntity.colors[1]);
        stack.translate(-font.width(currentText[0]) - translation, 0.0, 0.0);
    }

    private static void renderLineText(PhonographBlockEntity tileEntity, float partialTicks, MatrixStack stack, IRenderTypeBuffer buffers, int combinedLight, int combinedOverlay, int baseTransparency){
        switch (tileEntity.type){
            case 0:
            default:
                renderType0(tileEntity, partialTicks, stack, buffers, combinedLight, combinedOverlay, baseTransparency);
                break;
            case 1:
                renderType1(tileEntity, partialTicks, stack, buffers, combinedLight, combinedOverlay, baseTransparency);
                break;
        }
    }

    // Line 1: Fixed...
    private static void renderLine1(PhonographBlockEntity tileEntity, float partialTicks, MatrixStack stack, IRenderTypeBuffer buffers, int combinedLight, int combinedOverlay){
        if (currentLine != null)
            renderLineText(tileEntity, partialTicks, stack, buffers, combinedLight, combinedOverlay, Math.max(4, lineGradient));
        stack.translate(0.0, 10.0, 0.0);
    }

    // Line 2: Line fade in/out...
    private static void renderLine2(PhonographBlockEntity tileEntity, float partialTicks, MatrixStack stack, IRenderTypeBuffer buffers, int combinedLight, int combinedOverlay){
        if (currentLine != null) {
            double translation = (250 - lineGradient) * (fadingIn ? 0.01 : -0.01);
            stack.translate(translation, 0.0, 0.0);
            renderLineText(tileEntity, partialTicks, stack, buffers, combinedLight, combinedOverlay, Math.max(4, lineGradient)); // Full transparency(alpha=0) is somehow not supported, so we use a small alpha value instead...
            stack.translate(-translation, 0.0, 0.0);
        }
        stack.translate(0.0, 10.0, 0.0);
    }

    public static int getPlayedMilSecs(){
        return playedMilSecs;
    }

    public static void reset(){
        ready = false;
        lastMilSec = 0;
        playedMilSecs = 0;
        currentText[0] = "LOADING";
        currentText[1] = "...";
        currentText[2] = "";
    }

    public static void start(long gameTime){
        ready = true;
        lastMilSec = USE_SYSTEM_TIME ? System.currentTimeMillis() : gameTime * 50;
    }
}
